/*-------------------------------------------------------------------------
 *
 * view.c
 *	  使用重写规则构建视图
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/view.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/relation.h"
#include "access/xact.h"
#include "catalog/namespace.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "commands/view.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"

static void fc_checkViewTupleDesc(TupleDesc fc_newdesc, TupleDesc fc_olddesc);

/*---------------------------------------------------------------------
 * DefineVirtualRelation
 *
 * 创建一个视图关系并使用规则系统存储视图的查询。
 *
 * 必须已经调用了 EventTriggerAlterTableStart。
 *---------------------------------------------------------------------
 */
static ObjectAddress fc_DefineVirtualRelation(RangeVar *fc_relation, List *fc_tlist, bool fc_replace,
					  List *fc_options, Query *fc_viewParse)
{
	Oid			fc_viewOid;
	LOCKMODE	fc_lockmode;
	CreateStmt *fc_createStmt = makeNode(CreateStmt);
	List	   *fc_attrList;
	ListCell   *fc_t;

	/*
	 * 根据视图的 SELECT 列表中的名称和类型创建一个 ColumnDef 节点的列表
	 * （非垃圾的）目标列表项。
	 */
	fc_attrList = NIL;
	foreach(fc_t, fc_tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_t);

		if (!fc_tle->resjunk)
		{
			ColumnDef  *fc_def = makeColumnDef(fc_tle->resname,
											exprType((Node *) fc_tle->expr),
											exprTypmod((Node *) fc_tle->expr),
											exprCollation((Node *) fc_tle->expr));

			/*
			 * 可能该列是可排序类型，但无法解决排序规则，因此需进行双重检查。
			 */
			if (type_is_collatable(exprType((Node *) fc_tle->expr)))
			{
				if (!OidIsValid(fc_def->collOid))
					ereport(ERROR,
							(errcode(ERRCODE_INDETERMINATE_COLLATION),
							 errmsg("could not determine which collation to use for view column \"%s\"",
									fc_def->colname),
							 errhint("Use the COLLATE clause to set the collation explicitly.")));
			}
			else
				Assert(!OidIsValid(fc_def->collOid));

			fc_attrList = lappend(fc_attrList, fc_def);
		}
	}

	/*
	 * 查找、检查权限并锁定创建命名空间；还要检查是否
	 * 存在同名的预先存在的视图。这还将设置
	 * relation->relpersistence 为 RELPERSISTENCE_TEMP，如果选定的
	 * 命名空间是临时的。
	 */
	fc_lockmode = fc_replace ? AccessExclusiveLock : NoLock;
	(void) RangeVarGetAndCheckCreationNamespace(fc_relation, fc_lockmode, &fc_viewOid);

	if (OidIsValid(fc_viewOid) && fc_replace)
	{
		Relation	fc_rel;
		TupleDesc	fc_descriptor;
		List	   *fc_atcmds = NIL;
		AlterTableCmd *fc_atcmd;
		ObjectAddress fc_address;

		/* 关系已经被锁定，但我们必须构建一个 relcache 条目。 */
		fc_rel = relation_open(fc_viewOid, NoLock);

		/* 确保它确实是一个视图。 */
		if (fc_rel->rd_rel->relkind != RELKIND_VIEW)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is not a view",
							RelationGetRelationName(fc_rel))));

		/* 还要检查它是否已经在使用中 */
		CheckTableNotInUse(fc_rel, "CREATE OR REPLACE VIEW");

		/*
		 * 由于临时对象的命名空间可见性规则，我们
		 * 应该只会将一个临时视图替换为另一个
		 * 临时视图，对于永久视图也是类似。
		 */
		Assert(fc_relation->relpersistence == fc_rel->rd_rel->relpersistence);

		/*
		 * 创建一个元组描述符，以便与现有视图进行比较，并
		 * 验证旧的列列表是否是新的列列表的初始前缀。
		 */
		fc_descriptor = BuildDescForRelation(fc_attrList);
		fc_checkViewTupleDesc(fc_descriptor, fc_rel->rd_att);

		/*
		 * 如果添加了新属性，我们必须为它们添加 pg_attribute 条目。
		 * 使用 ALTER TABLE ADD COLUMN 基础设施来完成这项工作是方便的
		 * （尽管有点过于繁琐）。
		 *
		 * 请注意，我们必须在更新视图查询之前执行此操作，
		 * 因为规则系统要求在定义新规则时，正确的视图列必须到位。
		 *
		 * 还要注意，ALTER TABLE 不会在 AT_AddColumnToView
		 * 命令上运行解析转换。我们提供的 ColumnDef 必须准备好
		 * 直接执行。
		 */
		if (list_length(fc_attrList) > fc_rel->rd_att->natts)
		{
			ListCell   *fc_c;
			int			fc_skip = fc_rel->rd_att->natts;

			foreach(fc_c, fc_attrList)
			{
				if (fc_skip > 0)
				{
					fc_skip--;
					continue;
				}
				fc_atcmd = makeNode(AlterTableCmd);
				fc_atcmd->subtype = AT_AddColumnToView;
				fc_atcmd->def = (Node *) lfirst(fc_c);
				fc_atcmds = lappend(fc_atcmds, fc_atcmd);
			}

			/* EventTriggerAlterTableStart 由 ProcessUtilitySlow 调用 */
			AlterTableInternal(fc_viewOid, fc_atcmds, true);

			/* 使新的视图列可见 */
			CommandCounterIncrement();
		}

		/*
		 * 更新视图的查询。
		 *
		 * 请注意，我们必须在更新视图选项之前执行此操作，因为
		 * 新选项可能与旧视图查询不兼容（例如，如果我们试图添加
		 * WITH CHECK OPTION，我们要求新视图是自动可更新的，但旧视图可能
		 * 并非如此）。
		 */
		StoreViewQuery(fc_viewOid, fc_viewParse, fc_replace);

		/* 使新的视图查询可见 */
		CommandCounterIncrement();

		/*
		 * 更新视图的选项。
		 *
		 * 新的选项列表替换现有的选项列表，即使
		 * 它是空的。
		 */
		fc_atcmd = makeNode(AlterTableCmd);
		fc_atcmd->subtype = AT_ReplaceRelOptions;
		fc_atcmd->def = (Node *) fc_options;
		fc_atcmds = list_make1(fc_atcmd);

		/* EventTriggerAlterTableStart 由 ProcessUtilitySlow 调用 */
		AlterTableInternal(fc_viewOid, fc_atcmds, true);

		/*
		 * 这里需要更新视图的依赖关系的工作很少。
		 * 大多数视图级依赖关系，例如对所有者、模式和关联复合类型的依赖关系，并没有改变。
		 * 由于我们不允许更改现有视图列的类型或排序规则，因此现有列的依赖关系也不会改变，
		 * 而 AT_AddColumnToView 机制负责为新视图列添加这样的依赖关系。
		 * 视图查询的依赖关系可能已随意改变，但这在 StoreViewQuery 中处理了。
		 * 剩下的只是检查在创建扩展时是否允许替换视图。
		 */
		ObjectAddressSet(fc_address, RelationRelationId, fc_viewOid);

		recordDependencyOnCurrentExtension(&fc_address, true);

		/*
		 * 看起来可以，所以返回已存在视图的 OID。
		 */
		relation_close(fc_rel, NoLock);	/* 保持锁定! */

		return fc_address;
	}
	else
	{
		ObjectAddress fc_address;

		/*
		 * 为键/继承等设置参数。所有这些对视图来说都没有趣味...
		 */
		fc_createStmt->relation = fc_relation;
		fc_createStmt->tableElts = fc_attrList;
		fc_createStmt->inhRelations = NIL;
		fc_createStmt->constraints = NIL;
		fc_createStmt->options = fc_options;
		fc_createStmt->oncommit = ONCOMMIT_NOOP;
		fc_createStmt->tablespacename = NULL;
		fc_createStmt->if_not_exists = false;

		/*
		 * 创建关系（如果存在现有视图，这将出错，因此如果“替换”为假，我们不需要更多代码来抱怨）。
		 */
		fc_address = DefineRelation(fc_createStmt, RELKIND_VIEW, InvalidOid, NULL,
								 NULL);
		Assert(fc_address.objectId != InvalidOid);

		/* 使新的视图关系可见 */
		CommandCounterIncrement();

		/* 存储视图的查询 */
		StoreViewQuery(fc_address.objectId, fc_viewParse, fc_replace);

		return fc_address;
	}
}

/*
 * 验证与拟议的新视图定义相关的 tupledesc 是否与旧视图的 tupledesc 匹配。
 * 这基本上是 equalTupleDescs() 的简化版本，增加了生成特定投诉的代码。
 * 此外，我们允许新的 tupledesc 拥有比旧的更多列。
 */
static void fc_checkViewTupleDesc(TupleDesc fc_newdesc, TupleDesc fc_olddesc)
{
	int			fc_i;

	if (fc_newdesc->natts < fc_olddesc->natts)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot drop columns from view")));

	for (fc_i = 0; fc_i < fc_olddesc->natts; fc_i++)
	{
		Form_pg_attribute fc_newattr = TupleDescAttr(fc_newdesc, fc_i);
		Form_pg_attribute fc_oldattr = TupleDescAttr(fc_olddesc, fc_i);

		/* XXX 消息不正确，但我们不支持在视图上删除列 */
		if (fc_newattr->attisdropped != fc_oldattr->attisdropped)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot drop columns from view")));

		if (strcmp(NameStr(fc_newattr->attname), NameStr(fc_oldattr->attname)) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot change name of view column \"%s\" to \"%s\"",
							NameStr(fc_oldattr->attname),
							NameStr(fc_newattr->attname)),
					 errhint("Use ALTER VIEW ... RENAME COLUMN ... to change name of view column instead.")));

		/*
		 * 我们不能允许类型、类型修饰符或排序规则改变，因为这些属性可能嵌入在引用
		 * 该视图的其他视图/规则的 Vars 中。其他列属性可以忽略。
		 */
		if (fc_newattr->atttypid != fc_oldattr->atttypid ||
			fc_newattr->atttypmod != fc_oldattr->atttypmod)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot change data type of view column \"%s\" from %s to %s",
							NameStr(fc_oldattr->attname),
							format_type_with_typemod(fc_oldattr->atttypid,
													 fc_oldattr->atttypmod),
							format_type_with_typemod(fc_newattr->atttypid,
													 fc_newattr->atttypmod))));

		/*
		 * 在这一点上，attcollations 应该都是有效或无效的，因此无条件地应用 get_collation_name 应该没问题。
		 */
		if (fc_newattr->attcollation != fc_oldattr->attcollation)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot change collation of view column \"%s\" from \"%s\" to \"%s\"",
							NameStr(fc_oldattr->attname),
							get_collation_name(fc_oldattr->attcollation),
							get_collation_name(fc_newattr->attcollation))));
	}

	/*
	 * 我们忽略约束字段。新的视图描述不能有任何约束，而旧视图上唯一可能有的约束是默认值，
	 * 我们乐于将其保留。
	 */
}

static void fc_DefineViewRules(Oid fc_viewOid, Query *fc_viewParse, bool fc_replace)
{
	/*
	 * 设置 ON SELECT 规则。由于查询已经经过解析分析，我们直接使用 DefineQueryRewrite()。
	 */
	DefineQueryRewrite(pstrdup(ViewSelectRuleName),
					   fc_viewOid,
					   NULL,
					   CMD_SELECT,
					   true,
					   fc_replace,
					   list_make1(fc_viewParse));

	/*
	 * 总有一天：自动 ON INSERT 等
	 */
}

/*---------------------------------------------------------------
 * UpdateRangeTableOfViewParse
 *
 * 更新给定解析树的范围表。
 * 这次更新包括在范围表的开头添加两个新条目，
 * （否则规则系统将陷入缓慢、可怕和痛苦的死亡，我们现在可不想那样，对吧？）
 * 一个用于 OLD 关系，另一个用于 NEW 关系（它们实际上都指向
 * "view" 关系）。
 *
 * 当然，我们还必须将所有 Var 节点的 'varnos' 增加 2...
 *
 * 这些额外的 RT 条目在查询中实际上并不使用，
 * 除了运行时锁定和权限检查。
 *---------------------------------------------------------------
 */
static Query * fc_UpdateRangeTableOfViewParse(Oid fc_viewOid, Query *fc_viewParse)
{
	Relation	fc_viewRel;
	List	   *fc_new_rt;
	ParseNamespaceItem *fc_nsitem;
	RangeTblEntry *fc_rt_entry1,
			   *fc_rt_entry2;
	ParseState *fc_pstate;

	/*
	 * 创建给定解析树的副本。我们并不是不想在输入上涂鸦，
	 * 而是解析器有一个坏习惯，在像 BETWEEN 这样的构造中输出
	 * 多个指向相同子树的链接，我们不能让 OffsetVarNodes
	 * 将 Var 节点的 varno 增加两次。copyObject 将把任何多重引用的子树
	 * 扩展为多个副本。
	 */
	fc_viewParse = copyObject(fc_viewParse);

	/* 为 addRangeTableEntryForRelation 创建一个虚拟的 ParseState */
	fc_pstate = make_parsestate(NULL);

	/* 需要打开关系以用于 addRangeTableEntryForRelation */
	fc_viewRel = relation_open(fc_viewOid, AccessShareLock);

	/*
	 * 创建 2 个新的范围表条目并形成新的范围表...
	 * OLD 在前，然后是 NEW....
	 */
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_viewRel,
										   AccessShareLock,
										   makeAlias("old", NIL),
										   false, false);
	fc_rt_entry1 = fc_nsitem->p_rte;
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_viewRel,
										   AccessShareLock,
										   makeAlias("new", NIL),
										   false, false);
	fc_rt_entry2 = fc_nsitem->p_rte;

	/* 必须重写 addRangeTableEntry 的默认访问检查标志 */
	fc_rt_entry1->requiredPerms = 0;
	fc_rt_entry2->requiredPerms = 0;

	fc_new_rt = lcons(fc_rt_entry1, lcons(fc_rt_entry2, fc_viewParse->rtable));

	fc_viewParse->rtable = fc_new_rt;

	/*
	 * 现在将所有 var 节点和 jointree RT 索引偏移 2。
	 */
	OffsetVarNodes((Node *) fc_viewParse, 2, 0);

	relation_close(fc_viewRel, AccessShareLock);

	return fc_viewParse;
}

/*
 * DefineView
 *		执行 CREATE VIEW 命令。
 */
ObjectAddress DefineView(ViewStmt *fc_stmt, const char *fc_queryString,
		   int fc_stmt_location, int fc_stmt_len)
{
	RawStmt    *fc_rawstmt;
	Query	   *fc_viewParse;
	RangeVar   *fc_view;
	ListCell   *fc_cell;
	bool		fc_check_option;
	ObjectAddress fc_address;

	/*
	 * 执行解析分析，将原始解析树转换为查询。请注意，这也
	 * 会在源表上获得足够的锁。
	 */
	fc_rawstmt = makeNode(RawStmt);
	fc_rawstmt->stmt = fc_stmt->query;
	fc_rawstmt->stmt_location = fc_stmt_location;
	fc_rawstmt->stmt_len = fc_stmt_len;

	fc_viewParse = parse_analyze_fixedparams(fc_rawstmt, fc_queryString, NULL, 0, NULL);

	/*
	 * 语法应该确保结果是一个单一的 SELECT 查询。
	 * 然而，它并不禁止 SELECT INTO，因此我们必须检查这一点。
	 */
	if (!IsA(fc_viewParse, Query))
		elog(ERROR, "unexpected parse analysis result");
	if (fc_viewParse->utilityStmt != NULL &&
		IsA(fc_viewParse->utilityStmt, CreateTableAsStmt))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("views must not contain SELECT INTO")));
	if (fc_viewParse->commandType != CMD_SELECT)
		elog(ERROR, "unexpected parse analysis result");

	/*
	 * 检查不受支持的情况。这些测试与 DefineQueryRewrite() 中的测试
	 * 是冗余的，但该函数会抱怨虚假的 ON SELECT 规则，
	 * 我们更希望消息投诉关于视图的内容。
	 */
	if (fc_viewParse->hasModifyingCTE)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("views must not contain data-modifying statements in WITH")));

	/*
	 * 如果用户指定了 WITH CHECK OPTION，将其添加到 reloptions 列表中。
	 */
	if (fc_stmt->withCheckOption == LOCAL_CHECK_OPTION)
		fc_stmt->options = lappend(fc_stmt->options,
								makeDefElem("check_option",
											(Node *) makeString("local"), -1));
	else if (fc_stmt->withCheckOption == CASCADED_CHECK_OPTION)
		fc_stmt->options = lappend(fc_stmt->options,
								makeDefElem("check_option",
											(Node *) makeString("cascaded"), -1));

	/*
	 * 检查如果指定了 WITH CHECK OPTION，视图是否可自动更新。
	 */
	fc_check_option = false;

	foreach(fc_cell, fc_stmt->options)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_cell);

		if (strcmp(fc_defel->defname, "check_option") == 0)
			fc_check_option = true;
	}

	/*
	 * 如果指定了检查选项，查看视图是否实际上是可自动更新的。
	 */
	if (fc_check_option)
	{
		const char *fc_view_updatable_error =
		view_query_is_auto_updatable(fc_viewParse, true);

		if (fc_view_updatable_error)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("WITH CHECK OPTION is supported only on automatically updatable views"),
					 errhint("%s", _(fc_view_updatable_error))));
	}

	/*
	 * 如果给出了列名列表，则遍历并将这些插入到实际查询树中。
	 * - thomas 2000-03-08
	 */
	if (fc_stmt->aliases != NIL)
	{
		ListCell   *fc_alist_item = list_head(fc_stmt->aliases);
		ListCell   *fc_targetList;

		foreach(fc_targetList, fc_viewParse->targetList)
		{
			TargetEntry *fc_te = lfirst_node(TargetEntry, fc_targetList);

			/* 垃圾列不获取别名 */
			if (fc_te->resjunk)
				continue;
			fc_te->resname = pstrdup(strVal(lfirst(fc_alist_item)));
			fc_alist_item = lnext(fc_stmt->aliases, fc_alist_item);
			if (fc_alist_item == NULL)
				break;			/* 完成分配别名 */
		}

		if (fc_alist_item != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("CREATE VIEW specifies more column "
							"names than columns")));
	}

	/* 不可记录的视图没有意义。 */
	if (fc_stmt->view->relpersistence == RELPERSISTENCE_UNLOGGED)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("views cannot be unlogged because they do not have storage")));

	/*
	 * 如果用户没有明确要求临时视图，则检查我们是否隐式需要一个。
	 * 只要 CREATE 命令与此一致---没有显式的
	 * 架构名称，我们允许自动插入 TEMP。
	 */
	fc_view = copyObject(fc_stmt->view);	/* 不要破坏原始命令 */
	if (fc_view->relpersistence == RELPERSISTENCE_PERMANENT
		&& isQueryUsingTempRelation(fc_viewParse))
	{
		fc_view->relpersistence = RELPERSISTENCE_TEMP;
		ereport(NOTICE,
				(errmsg("view \"%s\" will be a temporary view",
						fc_view->relname)));
	}

	/*
	 * 创建视图关系
	 *
	 * 注意：如果已经存在且替换为 false，则事务将被
	 * 中止。
	 */
	fc_address = fc_DefineVirtualRelation(fc_view, fc_viewParse->targetList,
									fc_stmt->replace, fc_stmt->options, fc_viewParse);

	return fc_address;
}

/*
 * 使用规则系统存储视图的查询。
 */
void StoreViewQuery(Oid fc_viewOid, Query *fc_viewParse, bool fc_replace)
{
	/*
	 * 'viewParse' 的范围表不包含 "OLD"
	 * 和 "NEW" 关系的条目。所以... 添加它们！
	 */
	fc_viewParse = fc_UpdateRangeTableOfViewParse(fc_viewOid, fc_viewParse);

	/*
	 * 现在创建与视图关联的规则。
	 */
	fc_DefineViewRules(fc_viewOid, fc_viewParse, fc_replace);
}
